/* * Copyright (C) 2015-2017 Emanuel Moecklin * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.onegravity.rteditor; import android.graphics.Rect; import android.text.Layout; import android.text.Selection; import android.text.Spannable; import android.text.Spanned; import android.text.TextPaint; import android.text.method.ArrowKeyMovementMethod; import android.text.method.MovementMethod; import android.text.style.AbsoluteSizeSpan; import android.text.style.ClickableSpan; import android.text.style.LeadingMarginSpan; import android.view.MotionEvent; import android.widget.TextView; /** * ArrowKeyMovementMethod does support selection of text but not the clicking of links. * LinkMovementMethod does support clicking of links but not the selection of text. * This class adds the link clicking to the ArrowKeyMovementMethod. * We basically take the LinkMovementMethod onTouchEvent code and remove the line * Selection.removeSelection(buffer); * which de-selects all text when no link was found. */ public class RTEditorMovementMethod extends ArrowKeyMovementMethod { private static RTEditorMovementMethod sInstance; private static Rect sLineBounds = new Rect(); public static synchronized MovementMethod getInstance() { if (sInstance == null) { sInstance = new RTEditorMovementMethod(); } return sInstance; } @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { int index = getCharIndexAt(widget, event); if (index != -1) { ClickableSpan[] link = buffer.getSpans(index, index, ClickableSpan.class); if (link.length != 0) { if (action == MotionEvent.ACTION_UP) { link[0].onClick(widget); } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0])); } return true; } } /*else { Selection.removeSelection(buffer); }*/ } return super.onTouchEvent(widget, buffer, event); } // TODO finding links doesn't work with right alignment and potentially other formatting options private int getCharIndexAt(TextView textView, MotionEvent event) { // get coordinates int x = (int) event.getX(); int y = (int) event.getY(); x -= textView.getTotalPaddingLeft(); y -= textView.getTotalPaddingTop(); x += textView.getScrollX(); y += textView.getScrollY(); /* * Fail-fast check of the line bound. * If we're not within the line bound no character was touched */ Layout layout = textView.getLayout(); int line = layout.getLineForVertical(y); synchronized (sLineBounds) { layout.getLineBounds(line, sLineBounds); if (!sLineBounds.contains(x, y)) { return -1; } } // retrieve line text Spanned text = (Spanned) textView.getText(); int lineStart = layout.getLineStart(line); int lineEnd = layout.getLineEnd(line); int lineLength = lineEnd - lineStart; if (lineLength == 0) { return -1; } Spanned lineText = (Spanned) text.subSequence(lineStart, lineEnd); // compute leading margin and subtract it from the x coordinate int margin = 0; LeadingMarginSpan[] marginSpans = lineText.getSpans(0, lineLength, LeadingMarginSpan.class); if (marginSpans != null) { for (LeadingMarginSpan span : marginSpans) { margin += span.getLeadingMargin(true); } } x -= margin; // retrieve text widths float[] widths = new float[lineLength]; TextPaint paint = textView.getPaint(); paint.getTextWidths(lineText, 0, lineLength, widths); // scale text widths by relative font size (absolute size / default size) final float defaultSize = textView.getTextSize(); float scaleFactor = 1f; AbsoluteSizeSpan[] absSpans = lineText.getSpans(0, lineLength, AbsoluteSizeSpan.class); if (absSpans != null) { for (AbsoluteSizeSpan span : absSpans) { int spanStart = lineText.getSpanStart(span); int spanEnd = lineText.getSpanEnd(span); scaleFactor = span.getSize() / defaultSize; int start = Math.max(lineStart, spanStart); int end = Math.min(lineEnd, spanEnd); for (int i = start; i < end; i++) { widths[i] *= scaleFactor; } } } // find index of touched character float startChar = 0; float endChar = 0; for (int i = 0; i < lineLength; i++) { startChar = endChar; endChar += widths[i]; if (endChar >= x) { // which "end" is closer to x, the start or the end of the character? int index = lineStart + (x - startChar < endChar - x ? i : i + 1); //Logger.e(Logger.LOG_TAG, "Found character: " + (text.length()>index ? text.charAt(index) : "")); return index; } } return -1; } }